
package net.lab0.fractal.fractals.mandelbrot;


import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;

import net.lab0.fractal.abstracts.AbstractManageableUI2DFractal;
import net.lab0.fractal.classes.RenderingCoordinates;


public class BasicMandelbrotFractal
extends AbstractManageableUI2DFractal
{
    protected long[][] pixels = null;
    
    public BasicMandelbrotFractal(RenderingCoordinates viewCoordinates, long maxIteration, float progressUpdateInterval, long timeUpdateInterval)
    {
        super(viewCoordinates, maxIteration, progressUpdateInterval, timeUpdateInterval);
    }
    
    public void doComputation()
    {
        int compH;
        int compW;
        
        double xStep;
        double yStep;
        
        double xCenter;
        double yCenter;
        double viewportWidth;
        double viewportHeight;
        
        synchronized (getViewCoordinates())
        {
            compW = getRenderingCoordinates().getxResolution();
            compH = getRenderingCoordinates().getyResolution();
            
            if (pixels == null || compW != pixels.length || pixels.length == 0 || compH != pixels[0].length)
            {
                pixels = new long[compW][compH];
            }
            
            xStep = getRenderingCoordinates().getScaledWidth() / compW;
            yStep = getRenderingCoordinates().getScaledHeight() / compH;
            
            xCenter = getViewCoordinates().getxCenter();
            yCenter = getViewCoordinates().getyCenter();
            viewportWidth = getRenderingCoordinates().getScaledWidth();
            viewportHeight = getRenderingCoordinates().getScaledHeight();
        }
        
        // reset
        for (int y = 0; y < compH; y++)
        {
            for (int x = 0; x < compW; x++)
            {
                pixels[x][y] = 0;
            }
        }
        
        computationBlock(compH, compW, xStep, yStep, xCenter, yCenter, viewportWidth, viewportHeight);
    }

	protected void computationBlock(int compH, int compW, double xStep, double yStep, double xCenter, double yCenter, double viewportWidth, double viewportHeight)
	{
		for (int y = 0; y < compH; y++)
        {
            if (restartTest())
            {
                break;
            }
            pauseTest();
            
            setProgress((float) y / (float) compH);
            
            for (int x = 0; x < compW; x++)
            {
                long iter = 0;
                double x0, x1, y0, y1;
                x0 = x1 = xCenter - viewportWidth / 2 + xStep * x;
                y0 = y1 = yCenter + viewportHeight / 2 - yStep * y;
                // cardiod + 1st bulb test, cf wiki
                double q = (x0 - 0.25) * (x0 - 0.25) + y0 * y0;
                boolean inside = (q * (q + (x0 - 0.25)) < y0 * y0 * 0.25) || (((x0 + 1) * (x0 + 1) + y0 * y0) < 0.0625);
                if (!inside)
                {
                    while (x1 * x1 + y1 * y1 <= 4 && iter < maxIteration)
                    {
                        double tempX = x1 * x1 - y1 * y1 + x0;
                        y1 = 2 * x1 * y1 + y0;
                        x1 = tempX;
                        iter++;
                    }
                }
                else
                {
                    iter = maxIteration;
                }
                pixels[x][y] = iter;
            }
        }
	}
    
    @Override
    public BufferedImage renderImage()
    {
        int rendW;
        int rendH;
        synchronized (getViewCoordinates())
        {
            rendW = Math.max(getRenderingCoordinates().getxResolution(), 1);
            rendH = Math.max(getRenderingCoordinates().getyResolution(), 1);
        }
        
        GraphicsConfiguration ge = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
        BufferedImage bi = ge.createCompatibleImage(rendW, rendH, BufferedImage.TYPE_INT_RGB);
        
        // TODO : color parameters
        
        if (pixels != null && pixels.length > 0 && pixels[0].length > 0)
        {
            int compW = Math.min(rendW, pixels.length);
            int compH = Math.min(rendH, pixels[0].length);
            
            long minIter = pixels[0][0];
            long maxIter = pixels[0][0];
            for (int j = 0; j < compH; ++j)
            {
                for (int i = 0; i < compW; ++i)
                {
                    if (pixels[i][j] > maxIter)
                    {
                        maxIter = pixels[i][j];
                    }
                    else if (pixels[i][j] < minIter)
                    {
                        minIter = pixels[i][j];
                    }
                }
            }
            
            WritableRaster wr = bi.getRaster();
            float gain = 255f / (maxIter - minIter);
            float[] fArray = new float[3];
            for (int j = 0; j < compH; ++j)
            {
                for (int i = 0; i < compW; ++i)
                {
                    fArray[0] = fArray[1] = fArray[2] = ((float) pixels[i][j] - minIter) * gain;
                    wr.setPixel(i, j, fArray);
                }
            }
        }
        else
        {
            WritableRaster wr = bi.getRaster();
            float[] fArray = new float[3];
            int rayWidth = 5;
            for (int i = 0; i < rendW; ++i)
            {
                for (int j = 0; j < rendH; ++j)
                {
                    fArray[0] = fArray[1] = fArray[2] = ((i / rayWidth + j / rayWidth) % 2 == 0) ? 255f * 0.5f : 255f * 0.75f;
                    wr.setPixel(i, j, fArray);
                }
            }
        }
        
        return bi;
    }
    
    @Override
    public RenderingCoordinates getRenderingCoordinates()
    {
        return (RenderingCoordinates) getViewCoordinates();
    }
}
